#================================================
package MSN3::Notification;
#================================================

use strict;
use warnings;

# For Debugging
use Data::Dumper;

# IO
use IO::Socket;

# General
use MIME::Base64;
use Digest::MD5 qw(md5 md5_hex md5_base64);
use XML::LibXML;
use URI::Escape;

use Switch;

# Circles
use MSN3::Circle;

# Passport 3.0
use MSN3::Notification::PP3;
use MSN3::Notification::SOAP;
use MSN3::Notification::P2P;

sub checksum { my $o = tell(DATA); seek DATA,0,0; local $/; my $t = unpack("%32C*",<DATA>) % 65535;seek DATA,$o,0; return $t + MSN3::Notification::PP3::checksum() + MSN3::Notification::P2P::checksum() + MSN3::Notification::SOAP::checksum();};

sub new
{
    my $class = shift;
    my ($msn, $host, $port, $handle, $password) = (shift, shift, shift, shift, shift);

    my $self =
    {
        MSN             => $msn,
        SOAP            => new MSN3::Notification::SOAP($msn),
        PP3             => new MSN3::Notification::PP3($msn, $handle, $password),
        P2P             => new MSN3::Notification::P2P($msn),
        XML             => XML::LibXML->new(),

        Host            => $host,
        Port            => $port,
        Handle          => $handle,
        Password        => $password,

        TrID            => 0,

        Socket          => {},
        Type            => 'NS',

        PingTime        => time,
        PongTime        => time,

        Name            => '',
        PersonalMessage => '',

        Lists           =>
        {
            Allow           => {},
            Forward         => {},
            Block           => {},
            Reverse         => {},
            Pending         => {},
            Hide            => {}
        },
        Networks        => {},
        Groups          => {},
        Contacts        => {},

        PendingCircles  => {},
        Circles         => {},

        HopCount        => 1,

        ProductID       => 'PROD0120PW!CCV9@',
        ProductKey      => 'C1BX{V4W}Q3*10SM',

        PingTime        => time,
        PongTime        => time,
        PingIncrement   => 120,
        NoPongMax       => 240,

        @_
    };
    bless($self, $class);

    return $self;
}

sub DESTROY
{
    my $self = shift;

    # placeholder for possible destructor code
}

sub AUTOLOAD
{
    my $self = shift;

    my $method = $MSN3::Notification::AUTOLOAD;

    if( $method =~ /CMD_(.*)$/ )
    {
        $self->error( "$1 not handled in MSN3::Notification" );
    }
    else
    {
        $self->error( "method $method not defined" ) if( $self->{MSN}->{Error} );
    }
}

sub debug
{
    my $self = shift;

    return $self->{MSN}->debug( @_ );
}

sub error
{
    my $self = shift;

    return $self->{MSN}->error( @_ );
}

sub serverError
{
        my $self = shift;

        return $self->{MSN}->serverError( @_ );
}

#================================================
# Connect to the Notification Server
#================================================

sub connect
{
    my $self = shift;
    my $host = shift || $self->{Host};
    my $port = shift || $self->{Port};

    $self->{Socket} = new IO::Socket::INET(
        PeerAddr       => $host,
        PeerPort       => $port,
        Proto          => 'tcp'
    );

    # Connection error occured
    return $self->error( "Connection error: $!" ) if( !defined $self->{Socket} );

    $self->{MSN}->{Select}->add( $self->{Socket} );
    $self->{MSN}->{Connections}->{ $self->{Socket}->fileno } = $self;

    # Send Version
    $self->send( 'VER', $self->{MSN}->VER );

    return 1;
}

#================================================
# General functions
#================================================

sub disconnect
{
    my $self = shift;

    $self->debug( "Disconnecting from Notification Server" );

    return $self->_send( "OUT\r\n" );
}

sub ping
{
    my $self = shift;

    if( time >= $self->{PingTime} + $self->{PingIncrement} )
    {
        $self->{MSN}->triggerEvent( $self, "Ping" );

        $self->_send( "PNG\r\n" );
        $self->{PingTime} = time;

        if( time - $self->{PongTime} > $self->{NoPongMax} )
        {
            $self->debug( "Disconnected : No pong received from server" );
            $self->{MSN}->disconnect();
            $self->{MSN}->triggerEvent( $self, "Disconnected", "No pong received from server" );
            $self->{MSN}->connect() if( $self->{MSN}->{AutoReconnect} );
        }
    }
}

#================================================
# Send functions
#================================================

sub send
{
    my $self = shift;
    my $cmd  = shift || return $self->error( "No command specified to send" );
    my $data = shift;

    # Generate TrID using global TrID value...
    $self->{LastTrID} = $self->{TrID};
    my $datagram = $cmd . ' ' . $self->{TrID}++ . ' ' . $data . "\r\n";
    $self->_send($datagram);

    return $self->{LastTrID};
}

# Same as send without the "\r\n"
sub sendraw
{
    my $self = shift;
    my $cmd = shift || return $self->error( "No command specified to send" );
    my $data  = shift;

    # Generate TrID using global TrID value...
    $self->{LastTrID} = $self->{TrID};
    my $datagram = $cmd . ' ' . $self->{TrID}++ . ' ' . substr($data, 0, 2900);
    $self->_send($datagram);

    my $length = 2900;
    while($length < length($data))
    {
        $self->_send(substr($data, $length, 2900));
        $length += 2900;
    }

    return $self->{LastTrID};
}

sub sendload
{
    my $self = shift;
    my $cmd = shift;
    my @data  = @_;

    my $header = '';
    my $body = '';

    foreach my $data (@data)
    {
        if( ref($data) eq "HASH" )
        {
            $header .= "\r\n" if($header ne '');
            foreach my $key (keys %{$data})
            {
                $header .= $key .": ". $data->{ $key } ."\r\n";
            }
        }
        else
        {
            $body = $data;
        }
    }

    $self->sendraw( $cmd, length($header."\r\n".$body) ."\r\n". $header."\r\n".$body );
}

sub sendput
{
    my $self = shift;

    $self->sendload( 'PUT', @_ );
}

sub sendsdg
{
    my $self = shift;

    $self->sendload( 'SDG', @_ );
}

sub senddel
{
    my $self = shift;

    $self->sendload( 'DEL', @_ );
}

sub _send
{
    my $self = shift;
    my $msg = shift || return $self->error( "No message specified" );

    return $self->error( "Trying to print '$msg' on an undefined socket" ) if( !defined $self->{Socket} );

    # Send the data to the socket.
    $self->{Socket}->print( $msg );

    my $fn = $self->{Socket}->fileno;
    chomp($msg);

    print( "TX: $msg\n" ) if( $self->{MSN}->{ShowTX} );

    return length($msg);
}

#================================================
# Contact functions
#================================================

sub findNetwork
{
    my $self = shift;
    my $network = shift;

    switch($network)
    {
        case "WL"
        {
          return 1;
        }
        case "FB"
        {
          return 13;
        }
    }
}

sub getContact
{
    my $self = shift;
    my $handle = shift;

    my $network = undef;
    ($network, $handle) = split(":", $handle, 2) if($handle =~ /:/g);

    foreach my $contact (keys %{$self->{Contacts}})
    {
        if(defined $self->{Contacts}->{ $contact } && $self->{Contacts}->{ $contact }->{passportName} eq $handle)
        {
            if(!defined $network || ($self->findNetwork($self->{Contacts}->{ $contact }->{Network}) eq $network || $self->{Contacts}->{ $contact }->{Network} eq $network))
            {
                return $self->{Contacts}->{ $contact };
            }
        }
    }

    return undef;
}

sub allowContact
{
    my $self = shift;
    my $handle = shift || return 0;

    if( my $adl = $self->{SOAP}->AddMember( $handle, 'Allow' ) )
    {
        $self->sendraw( 'ADL', length($adl) . "\r\n" . $adl );
        return 1;
    }

    return 0;
}

sub disallowContact
{
    my $self = shift;
    my $handle = shift || return 0;

    if( my $rml = $self->{SOAP}->DeleteMember( $handle, 'Allow' ) )
    {
        $self->sendraw( 'RML', length($rml) . "\r\n" . $rml );
        return 1;
    }

    return 0;
}

sub blockContact
{
    my $self = shift;
    my $handle = shift || return 0;

    if( my $adl = $self->{SOAP}->AddMember( $handle, 'Hide' ) )
    {
        $self->sendraw( 'ADL', length($adl) . "\r\n" . $adl );
        return 1;
    }

    return 0;
}

sub unblockContact
{
    my $self = shift;
    my $handle = shift || return 0;

    if( my $rml = $self->{SOAP}->DeleteMember( $handle, 'Hide' ) )
    {
        $self->sendraw( 'RML', length($rml) . "\r\n" . $rml );
        return 1;
    }

    return 0;
}

#================================================
# Conversation functions
#================================================

sub findMember
{
    my $self = shift;
    my $handle = shift || return {};

    my $convos = {};
    foreach my $convo (values %{$self->getConvoList()})
    {
        if(defined $convo->{Roster}->{ $handle })
        {
            $convos->{ $convo->{FileNumber} } = $convo;
        }
    }

    return $convos;
}

sub getConvoList
{
    my $self = shift;

    my $convos = {};
    foreach my $convo (values %{$self->{Circles}})
    {
        $convos->{ $convo->{FileNumber} } = $convo;
    }

    return $convos;
}

sub getConvo
{
    my $self = shift;
    my $fileno = shift;

    foreach my $convo (values %{$self->{Circles}})
    {
        if($convo->{FileNumber} == $fileno)
        {
            return $convo;
        }
    }

    return undef;
}

sub getConvoByHandle
{
    my $self = shift;
    my $handle = shift;

    if( defined $self->{Circles}->{ $handle } )
    {
        return $self->{Circles}->{ $handle };
    }

    return undef;
}

sub sendBroadcast
{
    my $self = shift;

    foreach my $convo (values %{$self->getConvoList()})
    {
        $convo->sendMessage( @_ );
    }

    return 1;
}

#================================================
# Misc functions
#================================================

sub setName
{
    my $self = shift;
    my $name = shift || $self->{MSN}->{Handle};

    if( length($name) > 129 )
    {
        return $self->error( "Display name to long to set" );
    }

    $self->{Name} = $name;
    $self->setUserInfo();

    return 1;
}

sub setPersonalMessage
{
    my $self = shift;
    my $psm = shift;

    $self->{PersonalMessage} = $psm;
    $self->setUserInfo();

    return 1;
}

sub setStatus
{
    my $self = shift;
    my $status = shift || 'NLN';

    $self->{MSN}->{Status} = $status;
    $self->setUserInfo();

    return 1;
}

sub setUserInfo
{
    my $self = shift;

    my $body =
            '<user>'.
              '<s n="PE">'.
                '<UserTileLocation>'. $self->{P2P}->getObjectLocation( $self->{P2P}->{CurrentDP} ) .'</UserTileLocation>'.
                '<FriendlyName>'. $self->{Name} .'</FriendlyName>'.
                '<PSM>'. $self->{PersonalMessage} .'</PSM>'.
                '<RUM>'. $self->{PersonalMessage} .'</RUM>'.
                '<RLT>0</RLT>'.
              '</s>'.
              '<s n="IM">'.
                '<Status>'. $self->{MSN}->{Status} .'</Status>'.
                '<CurrentMedia></CurrentMedia>'.
              '</s>'.
              '<sep n="PD">'.
                '<ClientType>1</ClientType>'.
                '<EpName>MORPHIOUSBOT</EpName>'.
                '<Idle>false</Idle>'.
                '<State>'. $self->{MSN}->{Status} .'</State>'.
              '</sep>'.
              '<sep n="PE" epid="'. $self->{MSN}->{MachineGUID} .'">'.
                '<VER>MSNMSGR:15.4.3508.1109</VER>'.
                '<TYP>1</TYP>'.
                '<Capabilities>'. MSN3::Util::setCapabilities($self->{MSN}->{Capabilities}, 'PE', 0) .'</Capabilities>'.
              '</sep>'.
              '<sep n="IM">'.
                '<Capabilities>'. MSN3::Util::setCapabilities($self->{MSN}->{Capabilities}, 'IM') .'</Capabilities>'.
              '</sep>'.
            '</user>';

    $self->sendput(
        {
            Routing           => '1.0',
            To                => '1:'. $self->{MSN}->{Handle},
            From              => '1:'. $self->{MSN}->{Handle} .";epid=". $self->{MSN}->{MachineGUID},
        },
        {
            Reliability       => '1.0',
            Stream            => '1',
            Flags             => 'ACK',
        },
        {
            Publication       => '1.0',
            Uri               => '/user',
            'Content-Type'    => 'application/user+xml',
            'Content-Length'  => length($body),
        },
        $body
    );
}

#================================================
# Dispatch a server event to this object
#================================================

sub dispatch
{
    my $self = shift;
    my $incomingdata = shift || '';

    my ($cmd, @data) = split( / /, $incomingdata );

    if( !defined $cmd )
    {
        return $self->serverError( "Empty event received from server : '" . $incomingdata . "'" );
    }
    elsif( $cmd =~ /[0-9]+/ )
    {
        return $self->serverError( MSN3::Util::convertError( $cmd ) . " : " . @data );
    }
    else
    {
        my $c = "CMD_" . $cmd;

        no strict 'refs';
        &{$c}($self, @data);
    }
}

#================================================
# MSN Server messages handled by Notification
#================================================

sub CMD_VER
{
    my $self = shift;
    my @data = @_;

    $self->{protocol} = $data[1];
    $self->send( 'CVR', $self->{MSN}->CVER10 . ' ' . $self->{Handle} . ' ' . encode_base64("Version: 1\r\nXfrCount: ".$self->{HopCount},'') );

    return 1;
}

sub CMD_CVR
{
    my $self = shift;
    my @data = @_;

    $self->send( 'USR', 'SSO I ' . $self->{Handle});

    return 1;
}

sub CMD_XFR
{
    my $self = shift;
    my @data = @_;

    if( $data[1] eq 'NS' )
    {
        my ($host, $port) = split( /:/, $data[2] );
        $self->{Socket}->close();
        $self->{MSN}->{Select}->remove( $self->{Socket} );

        if( defined $self->{Socket}->fileno )
        {
            delete( $self->{MSN}->{Connections}->{ $self->{Socket}->fileno } );
        }

        $self->{HopCount}++;
        $self->connect( $host, $port );
    }

    return 1;
}

sub CMD_GCF
{
    my $self = shift;
    my @data = @_;

    if(length($self->{buf}) < $data[1])
    {
        $self->{buf} = $self->{line} . $self->{buf};
        return 'wait';
    }

    $self->{buf} = '';
    return 1;
}

sub CMD_USR
{
    my $self = shift;
    my @data = @_;

    if ($data[1] eq 'SSO' && $data[2] eq 'S')
    {
        my ($ticket, $token) = $self->{PP3}->authenticate($data[3], $data[4]);

        if (!defined $token || !defined $ticket )
        {
           $self->disconnect;
           return;
        }
        $self->send('USR', 'SSO S ' . $ticket . ' ' . $token . ' ' . $self->{MSN}->{MachineGUID});
    }
    elsif( $data[1] eq 'OK' )
    {
        my $friendly = $data[3];
    }
}

sub CMD_SBS
{
    my $self = shift;
    my @data = @_;
    # Currently unknown
}

sub CMD_QNG
{
    my $self = shift;

    $self->{PongTime} = time;

    $self->{MSN}->triggerEvent( $self, "Pong" );
}

sub CMD_ADL
{
    my $self = shift;
    my @data = @_;
    # Nothing to do
}

sub CMD_RML
{
    my $self = shift;
    my @data = @_;
    # Nothing to do
}

sub CMD_PUT
{
    my $self = shift;
    my @data = @_;

    if( length( $self->{buf} ) < $data[2] )
    {
        $self->{buf} = $self->{line} . $self->{buf};
        return "wait";
    }

    my ( $header, $msg ) = ( '', substr( $self->{buf}, 0, $data[2], "" ) );
    ($header, $msg) = $self->{MSN}->_strip_header($msg);

    if( defined $self->{PendingCircles}->{ $data[0] } )
    {
        my $circle = $self->{PendingCircles}->{ $data[0] };
        $circle->{To} = $header->{From};
        $self->{Circles}->{ $header->{From} } = $circle;

        delete( $self->{PendingCircles}->{ $data[0] } );
    }
}

sub CMD_NOT
{
    my $self = shift;
    my @data = @_;

    if( length( $self->{buf} ) < $data[0] )
    {
        $self->{buf} = $self->{line} . $self->{buf};
        return "wait";
    }

    $self->{buf} = '';
}


sub CMD_MSG
{
    my $self = shift;
    my ($user, $friendly, $length) = @_;

    if( length( $self->{buf} ) < $length )
    {
        $self->{buf} = $self->{line} . $self->{buf};
        return "wait";
    }

    my ( $header, $msg ) = ( '', substr( $self->{buf}, 0, $length, "" ) );
    ($header, $msg) = $self->{MSN}->_strip_header($msg);

    if( $header->{'Content-Type'} =~ /text\/x-msmsgsprofile/ )
    {
        $self->{MSN}->triggerEvent( $self, "UserProfile", $header );

        my $adl = $self->{SOAP}->loadInitial();
        $self->send( 'USR', 'SHA A ' . $self->{MSN}->{CircleTicket} );
        $self->sendraw( 'ADL', length($adl) . "\r\n" . $adl );

        $user = $self->getContact($self->{MSN}->{Handle});
        $user->{displayName} = $self->{MSN}->{Handle} if(!defined $user);
        $self->setName($user->{displayName});

        $self->setStatus('NLN');
        $self->{MSN}->triggerEvent( $self, "Connected" );
    }
    elsif( $header->{'Content-Type'} =~ /text\/x-msmsgsinitialmdatanotification/ ){}
}

#================================================
# Payload Message
#================================================

sub CMD_NFY
{
    my $self = shift;
    my @data = @_;

    if( length( $self->{buf} ) < $data[1] )
    {
        $self->{buf} = $self->{line} . $self->{buf};
        return "wait";
    }

    my ( $header, $msg ) = ( '', substr( $self->{buf}, 0, $data[1], "" ) );
    ($header, $msg) = $self->{MSN}->_strip_header($msg);

    #$self->debug(Dumper($header));
    $self->debug(Dumper($msg));

    if($data[0] eq "PUT")
    {
        if( $header->{'Content-Type'} =~ /application\/user\+xml/ )
        {
            my $contact = $self->getContact($header->{From});
            if(defined $contact)
            {
                my $dom = $self->{XML}->parse_string($msg);
                $contact->{displayName} = $dom->findvalue("//user/s[\@n='PE']/FriendlyName");
                $contact->{Status} = $dom->findvalue("//user/s[\@n='IM']/Status");
                $contact->{PersonalMessage} = $dom->findvalue("//user/s[\@n='PE']/PSM");
                $contact->{Capabilities} = $dom->findvalue("//user/sep[\@n='IM']/Capabilities");
            }
        }
        elsif( $header->{'Content-Type'} =~ /application\/circles\+xml/ )
        {
            my $circle = $self->getConvoByHandle($header->{From});
            if( !defined $circle )
            {
                $circle = new MSN3::Circle($self->{MSN}, $header->{From});
            }

            my $dom = $self->{XML}->parse_string($msg);

            foreach my $user ($dom->findnodes("//circle/roster/user"))
            {
                my ($network, $handle) = split(/:/, $user->findvalue("./id"), 2);
                if( $handle ne $self->{MSN}->{Handle} )
                {
                    $circle->addMember($handle, $network);
                }
                else
                {
                    $circle->accept() if( $user->findvalue("./state") eq "Pending" );

                    if( $circle->{MultiParty} eq 0 )
                    {
                        $circle->{MultiParty} = 1;
                        $circle->inviteall();
                    }
                }
            }
        }
        else
        {

            my $circle = $self->getConvoByHandle($header->{From});
            if( !defined $circle )
            {
                $circle = new MSN3::Circle($self->{MSN}, $header->{From});
            }

            $circle->dispatch($header, $msg);
        }
    }
    elsif($data[0] eq "DEL")
    {
        if( $header->{'Content-Type'} =~ /application\/user\+xml/ )
        {
            my $contact = $self->getContact($header->{'From-Handle'}, $header->{'From-Network'});
            $contact->{Status} = 'FLN';
            $contact->{MachineGuid} = '';

            foreach my $convo (values %{$self->getConvoList()})
            {
                $convo->removeMember($header->{'From-Handle'}, $header->{'From-Network'});
            }
        }
        elsif( $header->{'Content-Type'} =~ /application\/circles\+xml/ )
        {
            $header->{Uri} =~ /\/user\((.+)\:(.*)\)$/gi;
            my $network = $1;
            my $handle = $2;

            my $circle = $self->getConvoByHandle($header->{From});
            if( defined $circle )
            {
                $circle->removeMember($handle, $network);
            }
        }
    }
}

sub CMD_SDG
{
    my $self = shift;
    my @data = @_;

    if( length( $self->{buf} ) < $data[1] )
    {
        $self->{buf} = $self->{line} . $self->{buf};
        return "wait";
    }

    my ( $header, $msg ) = ( '', substr( $self->{buf}, 0, $data[1], "" ) );
    ($header, $msg) = $self->{MSN}->_strip_header($msg);

    $self->debug(Dumper($header));
    $self->debug(Dumper($msg));

    my $circle;
    if( $header->{'To-Network'} eq "10" )
    {
        $circle = $self->getConvoByHandle($header->{To});
        if( !defined $circle )
        {
            $circle = new MSN3::Circle($self->{MSN}, $header->{To});
        }
    }
    else
    {
        $circle = $self->getConvoByHandle($header->{From});
        if( !defined $circle )
        {
            $circle = new MSN3::Circle($self->{MSN}, $header->{From});
        }
    }

    $circle->dispatch($header, $msg);
}

#================================================
# MSN Challenge queries
#================================================

sub CMD_CHL
{
    my $self = shift;
    my @data = @_;

    my $hash = CreateQRYHash( $data[1], $self->{ProductID}, $self->{ProductKey} );
    $self->sendraw( 'QRY',  $self->{ProductID} . ' 32' . "\r\n" . $hash );
}

sub CMD_QRY
{
    my $self = shift;
    my @data = @_;
    # Nothing to do
}

#================================================
# MSN Challenge functions
#================================================

# This piece of code was written by Siebe Tolsma (Copyright 2004, 2005).
# Based on documentation by ZoRoNaX.
#
# This code is for eductional purposes only. Modification, use and/or publishing this code
# is entirely on your OWN risk, I can not be held responsible for any of the above.
# If you have questions please contact me by posting on the BOT2K3 forum: http://bot2k3.net/forum/

sub CreateQRYHash {
    use Math::BigInt;
  	my $chldata = shift || return;
  	my $prodid  = shift || return;
  	my $prodkey = shift || return;

  	# Create an MD5 hash out of the given data, then form 32 bit integers from it
  	my @md5hash = unpack("a16a16", md5_hex("$chldata$prodkey"));
  	my @md5parts = MD5HashToInt("$md5hash[0]$md5hash[1]");

  	# Then create a valid productid string, divisable by 8, then form 32 bit integers from it
  	my @chlprodid = CHLProdToInt("$chldata$prodid" . ("0" x (8 - length("$chldata$prodid") % 8)));

  	# Create the key we need to XOR
  	my $key = KeyFromInt(@md5parts, @chlprodid);

  	# Take the MD5 hash and split it in two parts and XOR them
  	my $low  = substr(Math::BigInt->new("0x$md5hash[0]")->bxor($key)->as_hex(), 2);
  	my $high = substr(Math::BigInt->new("0x$md5hash[1]")->bxor($key)->as_hex(), 2);

  	# Return the string, make sure both parts are padded though if needed
  	return ("0" x (16 - length($low))) . $low . ("0" x (16 - length($high))) . $high;
}

sub KeyFromInt {
  	# We take it the first 4 integers are from the MD5 Hash
  	my @md5 = splice(@_, 0, 4);
  	my @chlprod = @_;

  	# Create a new series of numbers
  	my $key_temp = Math::BigInt->new(0);
  	my $key_high = Math::BigInt->new(0);
  	my $key_low  = Math::BigInt->new(0);

  	# Then loop on the entries in the second array we got in the parameters
  	for(my $i = 0; $i < scalar(@chlprod); $i+=2) {
    		# Make $key_temp zero again and perform calculation as described in the documents
    		$key_temp->bzero()->badd($chlprod[$i])->bmul(0x0E79A9C1)->bmod(0x7FFFFFFF)->badd($key_high);
    		$key_temp->bmul($md5[0])->badd($md5[1])->bmod(0x7FFFFFFF);

    		# So, when that is done, work on the $key_high value :)
    		$key_high->bzero()->badd($chlprod[$i + 1])->badd($key_temp)->bmod(0x7FFFFFFF);
    		$key_high->bmul($md5[2])->badd($md5[3])->bmod(0x7FFFFFFF);

    		# And add the two parts to the low value of the key
    		$key_low->badd($key_temp)->badd($key_high);
  	}

  	# At the end of the loop we should add the dwords and modulo again
  	$key_high->badd($md5[1])->bmod(0x7FFFFFFF);
  	$key_low->badd($md5[3])->bmod(0x7FFFFFFF);

  	# Byteswap the keys, left shift (32) the high value and then add the low value
  	$key_low  = unpack("I*", reverse(pack("I*", $key_low )));
  	$key_high = unpack("I*", reverse(pack("I*", $key_high)));

  	return $key_temp->bzero()->badd($key_high)->blsft(32)->badd($key_low);
}

# Takes an CHLData + ProdID + Padded string and chops it in 4 bytes. Then converts to 32 bit integers
sub CHLProdToInt { return map { unpack("I*", $_) } unpack(("a4" x (length($_[0]) / 4)), $_[0]); }

# Takes an MD5 string and chops it in 4. Then "decodes" the HEX and converts to 32 bit integers. After that it ANDs
sub MD5HashToInt { return map { unpack("I*", pack("H*", $_)) & 0x7FFFFFFF } unpack(("a8" x 4), $_[0]); }

return 1;
__DATA__
